﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace FAST
{
    public class FileSystem
    {
        private List<FilesHandler> fileDetailsLeft;
        private List<FilesHandler> fileDetailsRight;
        private string[] folderLTempList;
        private string[] folderRTempList;

        private List<FilesHandler> osLeftFileDetails;
        private List<FilesHandler> osRightFileDetails;

        //GUI status rules
        private const int FILE_NOT_EXIST = 7; //copy file over
        private const int FILE_MODIFIED = 8;
        private const int FILE_IDENTICAL = 9;


        //Method name : FileSystem() : N.A for constructors.
        //Parameters  : None.
        //Description : initializes a List<FilesHandler> of both left and right folders. Sets List<FilesHandler> of OriginalState to default null. Initializes 2 List of strings which stores file path names with its content modified. These 2 data structure serves to propagate dirty bit changes made to a file to its root folder.
        public FileSystem()
        {
            fileDetailsLeft = new List<FilesHandler>();
            fileDetailsRight = new List<FilesHandler>();
            osLeftFileDetails = null;
            osRightFileDetails = null;

            folderLTempList = new string[0];//store files with content modified and propagate the dirty bit to its parent folder
            folderRTempList = new string[0];//store files with content modified and propagate the dirty bit to its parent folder
        }

        //Method name : setOSFileDetails() : void
        //Parameters  : osLeftList(type List<FilesHandler>), osRightList(type List<FilesHandler>)
        //Description : Sets List<FilesHandler> of OriginalState to a previously defined List<FilesHandler>, ie the original state of a folder kept as persistent data.
         public void setOSFileDetails(List<FilesHandler> osLeftList, List<FilesHandler> osRightList)
        {
            osLeftFileDetails = osLeftList;
            osRightFileDetails = osRightList;
        }

        //Method name : generateLeftFolderTree() : void
        //Parameters  : path(type string), rootPath(type string)
        //Description : if List<FilesHandler> of left OriginalState is null, recursively traverse through a folder to return its List<FilesHandler>. Else recursively traverse through a folder and at the same time compares the file with all files in the OriginalState, returning a List<FilesHandler> at the end.
        public void generateLeftFolderTree(string path, string rootPath)
        {
            string subPath = "";
            if (osLeftFileDetails == null) //OS dont exist (generate hash code for all files)
            {
                try
                {
                    foreach (string file in Directory.GetFiles(path))
                    {
                        FileHandler fileDetail = new FileHandler(file, file.Substring(rootPath.Length));

                        fileDetail.setMd5HashCode(computeHashCode(file));
                        fileDetailsLeft.Add(fileDetail);
                    }
                    foreach (string folder in Directory.GetDirectories(path))
                    {
                        FolderHandler folderDetail = new FolderHandler(folder, folder.Substring(rootPath.Length));
                        fileDetailsLeft.Add(folderDetail);

                        generateLeftFolderTree(folder, rootPath);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
            else //OS exist
            {
                foreach (string file in Directory.GetFiles(path))
                {
                    FileHandler fileDetail = new FileHandler(file, file.Substring(rootPath.Length));

                    fileDetail.setMd5HashCode(computeHashCode(file));

                    subPath = file.Substring(rootPath.Length);
                    int status = compareFolderToOriginal(rootPath, subPath, osLeftFileDetails, fileDetail);
                    //Note: recon need to transfer modified and newly added FileHandler from new leftORright folder list to OS list
                    //this is to ensure all FileHandler in OS has md5HashCode at all times

                    if (fileDetail.getDirtyBit())
                    {
                        subPath = subPath.Substring(0, subPath.LastIndexOf('\\'));
                        if (!subPath.Equals(""))
                        {
                            string[] tokens = subPath.Split('\\');
                            if (folderLTempList.Length < tokens.Length)
                                folderLTempList = tokens;
                        }
                    }

                    fileDetail.setFileStatus(status);
                    fileDetailsLeft.Add(fileDetail);
                }

                foreach (string folder in Directory.GetDirectories(path))
                {
                    subPath = folder.Substring(rootPath.Length);
                    FolderHandler folderDetail = new FolderHandler(folder, subPath);
                    int status2 = compareFolderToOriginal(rootPath, subPath, osLeftFileDetails, folderDetail);
                    fileDetailsLeft.Add(folderDetail);
                    int index = fileDetailsLeft.Count - 1;


                    generateLeftFolderTree(folder, rootPath);

                    if (folderLTempList.Length > 0)
                    {

                        string[] tokens2 = subPath.Split('\\');
                        int size = folderLTempList.Length - 1;

                        //to propagate same content dirty bit to root folders
                        if (folderLTempList[size].Equals(tokens2[tokens2.Length - 1]))
                        {
                            ((FolderHandler)fileDetailsLeft[index]).setDirtyBit(true);
                            
                            //Remove last element from array
                            string[] tempArr = new string[size];
                            for (int i = 0; i < size; i++)
                            {
                                tempArr[i] = folderLTempList[i];
                            }
                            folderLTempList = tempArr;
                            //End Remove
                        }

                    }
                }
            }

        }

        //Method name : generateRightFolderTree() : void
        //Parameters  : path(type string), rootPath(type string)
        //Description : if List<FilesHandler> of right OriginalState is null, recursively traverse through a folder and at the same compares each file with all files from the List<FilesHandler> obtain from generateLeftFolderTree to return a List<FilesHandler>. Else recursively traverse through a folder and at the same time compares the file with all files in the OriginalState, returning a  List<FilesHandler> at the end.
        public void generateRightFolderTree(string path, string rootPath)
        {
            string subPath = "";
            try
            {
                if (osRightFileDetails == null)//OS DONT EXIST
                {
                    foreach (string file in Directory.GetFiles(path))
                    {
                        FileHandler fileDetail = new FileHandler(file, file.Substring(rootPath.Length));

                        fileDetail.setMd5HashCode(computeHashCode(file));
                        int status = compareFileRightToLeftFiles(rootPath, file.Substring(rootPath.Length), fileDetail);
                        fileDetail.setFileStatus(status);
                        fileDetailsRight.Add(fileDetail);

                    }
                    foreach (string folder in Directory.GetDirectories(path))
                    {
                        FolderHandler folderDetail = new FolderHandler(folder, folder.Substring(rootPath.Length));

                        int status = compareFileRightToLeftFiles(folder, folder.Substring(rootPath.Length), folderDetail);
                        folderDetail.setFolderStatus(status);
                        fileDetailsRight.Add(folderDetail);

                        generateRightFolderTree(folder, rootPath);
                    }

                    if (fileDetailsLeft.Count > fileDetailsRight.Count)
                    {
                        int start = fileDetailsRight.Count;
                        int size = fileDetailsLeft.Count;
                        for (int i = start; i < size; i++)
                        {
                            if (!fileDetailsLeft[i].getFileType())
                            {
                                ((FolderHandler)fileDetailsLeft[i]).setDirtyBit(true);
                            }
                        }
                    }
                }
                else//OS EXIST
                {
                    foreach (string file in Directory.GetFiles(path))
                    {
                        FileHandler fileDetail = new FileHandler(file, file.Substring(rootPath.Length));

                        fileDetail.setMd5HashCode(computeHashCode(file));

                        subPath = file.Substring(rootPath.Length);
                        int status = compareFolderToOriginal(rootPath, subPath, osRightFileDetails, fileDetail);

                        if (fileDetail.getDirtyBit())
                        {
                            subPath = subPath.Substring(0, subPath.LastIndexOf('\\'));
                            if (!subPath.Equals(""))
                            {
                                string[] tokens = subPath.Split('\\');
                                if (folderRTempList.Length < tokens.Length)
                                    folderRTempList = tokens;
                            }
                        }

                        fileDetail.setFileStatus(status);
                        fileDetailsRight.Add(fileDetail);
                    }
                    foreach (string folder in Directory.GetDirectories(path))
                    {
                        subPath = folder.Substring(rootPath.Length);
                        FolderHandler folderDetail = new FolderHandler(folder, subPath);
                        int status2 = compareFolderToOriginal(rootPath, subPath, osRightFileDetails, folderDetail);
                        fileDetailsRight.Add(folderDetail);
                        int index = fileDetailsRight.Count - 1;

                        generateRightFolderTree(folder, rootPath);

                        if (folderRTempList.Length > 0) //virtual folder dirty list 
                        {

                            string[] tokens2 = subPath.Split('\\');
                            int size = folderRTempList.Length - 1;

                            //to propagate same content to root folders
                            if (folderRTempList[size].Equals(tokens2[tokens2.Length - 1]))
                            {
                                ((FolderHandler)fileDetailsRight[index]).setDirtyBit(true);

                                //Remove last element from array
                                string[] tempArr = new string[size];
                                for (int i = 0; i < size; i++)
                                {
                                    tempArr[i] = folderRTempList[i];
                                }
                                folderRTempList = tempArr;
                                //End Remove
                            }

                        }
                    }
                }
            }
            catch (Exception)
            {
            }
        }

        //Method name : compareFolderToOriginal() : int
        //Parameters  : fullPath(type string), subPath(type string), osFileDetails(type List<FilesHandler>), fileDetail/*can be left or right file or folder*/(type FilesHandler)
        //Description : This method basically accepts a file and compare it with all files from an original state. The algorithm in this method is described later in the guide in Section 4.1 UpdateDetection.
        //Exception : DirectoryNotFoundException
        public int compareFolderToOriginal(string fullPath, string subPath, List<FilesHandler> osFileDetails, FilesHandler fileDetail/*can be left or right file or folder*/)
        {
            bool folderDirtyBit = true;
            int REPORT = FILE_NOT_EXIST;

            try
            {
                if (osFileDetails != null)
                {
                    foreach (FilesHandler fh in osFileDetails)//os file details
                    {
                        if (fh.getFileType() && fileDetail.getFileType()) //check if its a file, true = file
                        {
                            if (subPath.Equals(((FileHandler)fh).getSubFilePath()))//check file name and location exist in both folders
                            {
                                ((FileHandler)fileDetail).setVersioningIdx(((FileHandler)fh).getVersioningIdx());
                                ((FileHandler)fileDetail).setIsInOS(true);

                                //check file size and file timestamp
                                if ((((FileHandler)fileDetail).getFileLength() == ((FileHandler)fh).getFileLength()) && (((FileHandler)fileDetail).getLastModTime() == ((FileHandler)fh).getLastModTime()))
                                {
                                    //perform md5 check
                                    if (((FileHandler)fileDetail).getMd5HashCode().Equals(((FileHandler)fh).getMd5HashCode()))
                                    {
                                        ((FileHandler)fileDetail).setDirtyBit(false);
                                        REPORT = FILE_IDENTICAL;
                                    }
                                    else
                                    {
                                        MessageBox.Show("hash code error: " + ((FileHandler)fileDetail).getMd5HashCode() + "-->" + ((FileHandler)fh).getMd5HashCode());//debug
                                        //Conflict
                                        REPORT = FILE_MODIFIED;
                                        ((FileHandler)fileDetail).setIsTimeConflict(true);
                                    }
                                }
                                else
                                {
                                    REPORT = FILE_MODIFIED;
                                    //if current file timestamp less than OS -> raise conflict
                                    if ((((FileHandler)fileDetail).getLastModTime() < ((FileHandler)fh).getLastModTime()))
                                        ((FileHandler)fileDetail).setIsTimeConflict(true);
                                }
                                break;
                            }

                        }

                        else if (!(fh.getFileType() || fileDetail.getFileType()))//if folder (De Morgan's law !(p OR q) == !p AND !q )
                        {
                            if (subPath.Equals(((FolderHandler)fh).getSubFolderPath()))//check file name and location exist in both folders
                            {
                                folderDirtyBit = false;
                                break;
                            }
                        }
                    }

                    if (!fileDetail.getFileType())
                        ((FolderHandler)fileDetail).setDirtyBit(folderDirtyBit);
                }
            }
            catch (DirectoryNotFoundException)
            {
            }
            finally
            {
                MessageBox.Show("Original State do not exist!"); 
            }
            return REPORT;
        }


        //Method name : compareFileRightToLeftFiles() : int
        //Parameters  : fullPath(type string), subPath(type string), rightfileDetail(type FilesHandler)
        //Description : This method basically accepts a file and compare it with all files from the source (left) folder. The algorithm in this method is described later in the guide in Section 4.1 UpdateDetection.
        //Exception : DirectoryNotFoundException
        public int compareFileRightToLeftFiles(string fullPath, string subPath, FilesHandler rightfileDetail)
        {
            bool isFolderExist = true;
            int REPORT = FILE_NOT_EXIST;
            int index = 0;
            foreach (FilesHandler fh in fileDetailsLeft)//left fileDetails
            {
                if (fh.getFileType() && rightfileDetail.getFileType()) //check if its a file, true = file
                {

                    if (subPath.Equals(((FileHandler)fh).getSubFilePath()))//check file name and location exist in both folders
                    {
                        //check file size and file timestamp
                        if ((((FileHandler)rightfileDetail).getFileLength() == ((FileHandler)fh).getFileLength()) && (((FileHandler)rightfileDetail).getLastModTime() == ((FileHandler)fh).getLastModTime()))
                        {
                            //perform md5 check
                            if (((FileHandler)rightfileDetail).getMd5HashCode().Equals(((FileHandler)fh).getMd5HashCode()))
                            {
                                REPORT = FILE_IDENTICAL;
                                ((FileHandler)rightfileDetail).setDirtyBit(false);
                                ((FileHandler)fileDetailsLeft[index]).setDirtyBit(false);
                            }
                            else
                            {
                                REPORT = FILE_MODIFIED;
                                MessageBox.Show("hash code error: " + ((FileHandler)rightfileDetail).getMd5HashCode() + "-->" + ((FileHandler)fh).getMd5HashCode());
                            }
                        }
                        else
                        {
                            REPORT = FILE_MODIFIED;
                            if ((((FileHandler)rightfileDetail).getLastModTime() > ((FileHandler)fh).getLastModTime()))
                                //rightfileDetail.setDirtyBit(true);
                                ((FileHandler)fileDetailsLeft[index]).setDirtyBit(false);
                            else
                                //((FileHandler)fileDetailsLeft[index]).setDirtyBit(true);
                                ((FileHandler)rightfileDetail).setDirtyBit(false);

                        }
                        ((FileHandler)fileDetailsLeft[index]).setFileStatus(REPORT);//set file status in left folder list
                        break;
                    }

                }
                else if (!(fh.getFileType() || rightfileDetail.getFileType()))//if folder (De Morgan's law !(p OR q) == !p AND !q )
                {
                    if (subPath.Equals(((FolderHandler)fh).getSubFolderPath()))//check file name and location exist in both folders
                    {
                        REPORT = FILE_IDENTICAL; //FOR USE IN FOLDER COMPARE
                        ((FolderHandler)fileDetailsLeft[index]).setFolderStatus(REPORT);//set folder status in left folder list

                        isFolderExist = false;
                        ((FolderHandler)fileDetailsLeft[index]).setDirtyBit(false);
                        break;
                    }
                }
                index++;
            }

            if (!rightfileDetail.getFileType())
            {
                ((FolderHandler)rightfileDetail).setDirtyBit(isFolderExist);
            }

            return REPORT;
        }

        //Method name : getLeftFileDetails() : List<FilesHandler>
        //Parameters  : None.
        //Description : returns a List<FilesHandler> obtain from generateLeftFolderTree
        //Exceptions  : None.
        public List<FilesHandler> getLeftFileDetails()
        {
            return fileDetailsLeft;
        }

        //Method name : getRightFileDetails() : List<FilesHandler>
        //Parameters  : None.
        //Description : returns a List<FilesHandler> obtain from generateRightFolderTree
        //Exceptions  : None.
        public List<FilesHandler> getRightFileDetails()
        {
            return fileDetailsRight;
        }

        //execute this method after checking modified time and size
        //*******************************************************************************//

        //Method name : md5Compare() : Boolean
        //Parameters  : filepath3(type String), filepath4(type String)
        //Description : Accepts 2 file paths, generates the md5 hash code of both file from method computeHashCode and lastly perform a string comparison of the hash code generated. This method serves to identify if the file content of 2 files are identical.
        //Exceptions  : DirectoryNotFoundException
        public Boolean md5Compare(String filepath3, String filepath4) //MD5 Hash Check
        {
            String fileName1 = filepath3;
            String fileName2 = filepath4;

            bool result3 = new FileInfo(fileName1).Length == new FileInfo(fileName2).Length;

            if (result3)
            {
                byte[] a = GetHash(fileName1);
                byte[] b = GetHash(fileName2);

                for (int i = 0; i < a.Length; i++)
                {
                    if (a[i] != b[i])
                    {
                        result3 = false;
                        break;
                    }
                }
            }
            return result3;
        }
        
        //Method name : computeHashCode() : string
        //Parameters  : f(type string)
        //Description : this method computes the md5 hash code of a file given its full file path
        //Exceptions  : NullException
        public string computeHashCode(string f)
        {
            System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
            StringBuilder sb = new StringBuilder();

            using (FileStream fs = File.Open(f, FileMode.Open))
            {
                foreach (byte b in md5.ComputeHash(fs))
                    sb.Append(b.ToString("x2").ToLower());
            }

            return sb.ToString();
        }

        public byte[] GetHash(string fileName)
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Open))
            {
                return System.Security.Cryptography.MD5.Create().ComputeHash(fs);
            }
        }


    }
}
